using System;
using System.Collections.Generic;
using UnityEngine;

namespace GGPhysics
{
	public class CollisionDetection
	{
		public delegate bool CollisionDetectionAlgorithm(ColliderBody body1, ColliderBody body2, out Collision collision);

		public class CollisionStats
		{
			public int ballBallCheck;

			public int ballLineCheck;

			public int ballBallAABBFail;

			public int ballLineAABBFail;

			public int sweepItems;

			public int sweepChecks;

			public int sortIterations;

			public int sweepPotentialCollisions;

			public void Clear()
			{
				sweepItems = (sweepChecks = (sweepPotentialCollisions = (ballBallCheck = (ballLineCheck = (ballBallAABBFail = (ballLineAABBFail = 0))))));
			}
		}

		public struct AABBBounds
		{
			public Vector2 bottomLeftBound;

			public Vector2 topRightBound;

			public AABBBounds(Vector3 lineStart, Vector3 lineEnd)
			{
				bottomLeftBound = Vector2.zero;
				bottomLeftBound.x = ((!(lineStart.x < lineEnd.x)) ? lineEnd.x : lineStart.x);
				bottomLeftBound.y = ((!(lineStart.z < lineEnd.z)) ? lineEnd.z : lineStart.z);
				topRightBound = Vector2.zero;
				topRightBound.x = ((!(lineStart.x > lineEnd.x)) ? lineEnd.x : lineStart.x);
				topRightBound.y = ((!(lineStart.z > lineEnd.z)) ? lineEnd.z : lineStart.z);
			}

			public AABBBounds(Vector3 sphereStart, Vector3 sphereEnd, float radius)
			{
				Vector2 vector = new Vector2(MathEx.Min(sphereStart.x + radius, sphereStart.x - radius), MathEx.Min(sphereStart.z + radius, sphereStart.z - radius));
				Vector2 vector2 = new Vector2(MathEx.Max(sphereStart.x + radius, sphereStart.x - radius), MathEx.Max(sphereStart.z + radius, sphereStart.z - radius));
				Vector2 vector3 = new Vector2(MathEx.Min(sphereEnd.x + radius, sphereEnd.x - radius), MathEx.Min(sphereEnd.z + radius, sphereEnd.z - radius));
				Vector2 vector4 = new Vector2(MathEx.Max(sphereEnd.x + radius, sphereEnd.x - radius), MathEx.Max(sphereEnd.z + radius, sphereEnd.z - radius));
				bottomLeftBound = new Vector2(MathEx.Min(vector.x, vector3.x), MathEx.Min(vector.y, vector3.y));
				topRightBound = new Vector2(MathEx.Max(vector2.x, vector4.x), MathEx.Max(vector2.y, vector4.y));
			}

			public static bool CanCollideY(AABBBounds bounds1, AABBBounds bounds2)
			{
				if (bounds1.bottomLeftBound.y > bounds2.topRightBound.y || bounds2.bottomLeftBound.y > bounds1.topRightBound.y)
				{
					return false;
				}
				return true;
			}

			public static bool CanCollide(ref AABBBounds bounds1, ref AABBBounds bounds2)
			{
				if (bounds1.bottomLeftBound.x > bounds2.topRightBound.x)
				{
					return false;
				}
				if (bounds2.bottomLeftBound.x > bounds1.topRightBound.x)
				{
					return false;
				}
				if (bounds1.bottomLeftBound.y > bounds2.topRightBound.y)
				{
					return false;
				}
				if (bounds2.bottomLeftBound.y > bounds1.topRightBound.y)
				{
					return false;
				}
				return true;
			}
		}

		public static Dictionary<Type, Dictionary<Type, CollisionDetectionAlgorithm>> collisionJumpTable;

		private static float epsilon;

		public static CollisionStats stats;

		static CollisionDetection()
		{
			epsilon = 1E-05f;
			stats = new CollisionStats();
			collisionJumpTable = new Dictionary<Type, Dictionary<Type, CollisionDetectionAlgorithm>>();
			collisionJumpTable.Add(typeof(SphereShape), new Dictionary<Type, CollisionDetectionAlgorithm>());
			collisionJumpTable[typeof(SphereShape)][typeof(LineShape)] = TryGetSphereLineCollision;
			collisionJumpTable[typeof(SphereShape)][typeof(SphereShape)] = TryGetSphereSphereCollision;
			collisionJumpTable[typeof(SphereShape)][typeof(CompositeLineShape)] = TryGetSphereCompositeLineCollision;
		}

		public static bool TryGetCollision(ColliderBody body1, ColliderBody body2, out Collision collision)
		{
			if (body1.body.shape == null || body2.body.shape == null)
			{
				collision = default(Collision);
				return false;
			}
			Shape shape = body1.body.shape;
			Shape shape2 = body2.body.shape;
			if (!(shape is SphereShape))
			{
				Shape shape3 = shape;
				shape = shape2;
				shape2 = shape3;
				ColliderBody colliderBody = body1;
				body1 = body2;
				body2 = colliderBody;
			}
			if (!(shape is SphereShape))
			{
				collision = default(Collision);
				return false;
			}
			if (shape2 is SphereShape)
			{
				return TryGetSphereSphereCollision(body1, body2, out collision);
			}
			if (shape2 is CompositeLineShape)
			{
				return TryGetSphereCompositeLineCollision(body1, body2, out collision);
			}
			if (shape2 is LineShape)
			{
				return TryGetSphereLineCollision(body1, body2, out collision);
			}
			collision = default(Collision);
			return false;
		}

		private static bool IsEqual(float a, float b)
		{
			return Mathf.Abs(a - b) < epsilon;
		}

		private static bool IsLessOrEqual(float a, float b)
		{
			return a < b || IsEqual(a, b);
		}

		private static bool IsGreaterOrEqual(float a, float b)
		{
			return a > b || IsEqual(a, b);
		}

		protected static bool IsBetweenInclusive(Vector3 start, Vector3 end, Vector3 point)
		{
			return IsGreaterOrEqual(point.x, MathEx.Min(start.x, end.x)) && IsLessOrEqual(point.x, MathEx.Max(start.x, end.x)) && IsGreaterOrEqual(point.z, MathEx.Min(start.z, end.z)) && IsLessOrEqual(point.z, MathEx.Max(start.z, end.z));
		}

		protected static bool TryGetSphereCompositeLineCollision(ColliderBody body1, ColliderBody body2, out Collision collision)
		{
			AABBBounds bounds = body2.bounds;
			AABBBounds bounds2 = body1.bounds;
			if (!AABBBounds.CanCollide(ref bounds2, ref bounds))
			{
				collision = default(Collision);
				stats.ballLineAABBFail++;
				return false;
			}
			CompositeLineShape shape = body2.body.GetShape<CompositeLineShape>();
			collision = default(Collision);
			Collision collision2 = default(Collision);
			for (int i = 0; i < shape.lines.Count; i++)
			{
				LineShape lineShape = shape.lines[i];
				if (TryGetSphereLineCollision2(body1, body2, lineShape, out collision))
				{
					if (!collision2.isColliding)
					{
						collision2 = collision;
					}
					else if (collision2.timeOfImpact > collision.timeOfImpact)
					{
						collision2 = collision;
					}
				}
			}
			collision = collision2;
			return collision2.isColliding;
		}

		protected static bool TryGetSphereLineCollision(ColliderBody body1, ColliderBody body2, out Collision collision)
		{
			LineShape lineShape = body2.body.shape as LineShape;
			return TryGetSphereLineCollision2(body1, body2, lineShape, out collision);
		}

		protected static bool TryGetSphereLineCollision2(ColliderBody body1, ColliderBody body2, LineShape lineShape, out Collision collision)
		{
			PhysicsBody body3 = body1.body;
			PhysicsBody body4 = body2.body;
			collision = default(Collision);
			SphereShape sphereShape = body3.shape as SphereShape;
			Vector3 vector = body4.currentTransform.TransformLocalToWorld(lineShape.point1).OnGround();
			Vector3 vector2 = body4.currentTransform.TransformLocalToWorld(lineShape.point2).OnGround();
			AABBBounds bounds = new AABBBounds(vector, vector2);
			Vector3 vector3 = body3.currentTransform.TransformLocalToWorld(sphereShape.position).OnGround();
			PhysicsBody.Transform nextTransform = body1.nextTransform;
			Vector3 vector4 = nextTransform.TransformLocalToWorld(sphereShape.position).OnGround();
			AABBBounds bounds2 = new AABBBounds(vector3, vector4, sphereShape.radius);
			if (!AABBBounds.CanCollide(ref bounds, ref bounds2))
			{
				stats.ballLineAABBFail++;
				return false;
			}
			stats.ballLineCheck++;
			Vector3 intersectionBetweenLines = GetIntersectionBetweenLines(vector, vector2, vector3, vector4);
			if (intersectionBetweenLines.x == float.PositiveInfinity || intersectionBetweenLines.y == float.PositiveInfinity || intersectionBetweenLines.z == float.PositiveInfinity)
			{
				return false;
			}
			Vector3 closestLineApproachToAPoint = GetClosestLineApproachToAPoint(vector, vector2, vector3);
			Vector3 closestLineApproachToAPoint2 = GetClosestLineApproachToAPoint(vector, vector2, vector4);
			Vector3 closestLineApproachToAPoint3 = GetClosestLineApproachToAPoint(vector3, vector4, vector);
			Vector3 closestLineApproachToAPoint4 = GetClosestLineApproachToAPoint(vector3, vector4, vector2);
			if (IsBetweenInclusive(vector, vector2, intersectionBetweenLines) && IsBetweenInclusive(vector3, vector4, intersectionBetweenLines))
			{
				collision = GetCollisionSphereLine(body3, body4, intersectionBetweenLines, sphereShape.radius, vector3, vector4, vector, vector2, closestLineApproachToAPoint);
			}
			else if (IsBetweenInclusive(vector, vector2, closestLineApproachToAPoint2) && (vector4 - closestLineApproachToAPoint2).magnitude <= sphereShape.radius)
			{
				collision = GetCollisionSphereLine(body3, body4, intersectionBetweenLines, sphereShape.radius, vector3, vector4, vector, vector2, closestLineApproachToAPoint);
			}
			else if (IsBetweenInclusive(vector3, vector4, closestLineApproachToAPoint3) && (vector - closestLineApproachToAPoint3).magnitude <= sphereShape.radius)
			{
				collision = GetCollisionSphereLine(body3, body4, intersectionBetweenLines, sphereShape.radius, vector3, vector4, vector, vector2, closestLineApproachToAPoint);
			}
			else if (IsBetweenInclusive(vector3, vector4, closestLineApproachToAPoint4) && (vector2 - closestLineApproachToAPoint4).magnitude <= sphereShape.radius)
			{
				collision = GetCollisionSphereLine(body3, body4, intersectionBetweenLines, sphereShape.radius, vector3, vector4, vector, vector2, closestLineApproachToAPoint);
			}
			else if (!TryGetSphereSphereCollision(body3, body4, vector3, vector4, sphereShape.radius, vector2, vector2, 0f, out collision) && !TryGetSphereSphereCollision(body3, body4, vector3, vector4, sphereShape.radius, vector, vector, 0f, out collision))
			{
			}
			if (collision.isColliding)
			{
				collision.body1.transformAtCollision.rotation = Quaternion.Lerp(body1.body.currentTransform.rotation, body1.nextTransform.rotation, collision.timeOfImpact);
				collision.body2.transformAtCollision.rotation = Quaternion.Lerp(body2.body.currentTransform.rotation, body2.nextTransform.rotation, collision.timeOfImpact);
			}
			collision.SetMotionsBeforeCollisions();
			return collision.isColliding;
		}

		private static Collision GetCollisionSphereLine(PhysicsBody sphere, PhysicsBody line, Vector3 movementIntersection, float radius, Vector3 sphereStart, Vector3 sphereEnd, Vector3 lineStart, Vector3 lineEnd, Vector3 closestPointOnLineToStartMove)
		{
			Collision collision = Collision.indentity;
			Vector3 vector = movementIntersection - radius * (movementIntersection - sphereStart).magnitude / (closestPointOnLineToStartMove - sphereStart).magnitude * (sphereEnd - sphereStart).normalized;
			Vector3 closestLineApproachToAPoint = GetClosestLineApproachToAPoint(lineStart, lineEnd, vector);
			if (!IsBetweenInclusive(lineStart, lineEnd, closestLineApproachToAPoint))
			{
				Vector3 lhs = lineEnd - lineStart;
				Vector3 rhs = closestLineApproachToAPoint - lineStart;
				if (Vector3.Dot(lhs, rhs) > 0f)
				{
					closestLineApproachToAPoint = lineEnd;
					if (TryGetSphereSphereCollision(sphere, line, sphereStart, sphereEnd, radius, lineEnd, lineEnd, 0f, out collision))
					{
						return collision;
					}
				}
				else
				{
					closestLineApproachToAPoint = lineStart;
					if (TryGetSphereSphereCollision(sphere, line, sphereStart, sphereEnd, radius, lineStart, lineStart, 0f, out collision))
					{
						return collision;
					}
				}
				return collision;
			}
			if (!collision.isColliding)
			{
				collision = Collision.indentity;
			}
			Vector3 vector2 = sphereEnd - sphereStart;
			float timeOfImpact = 0f;
			if (sphereEnd != sphereStart)
			{
				timeOfImpact = Mathf.Abs(Vector3.Dot(vector - sphereStart, vector2.normalized) / vector2.magnitude);
			}
			collision.body1.body = sphere;
			collision.body1.transformAtCollision.position = vector;
			collision.body2.body = line;
			collision.body2.transformAtCollision.position = line.currentTransform.position;
			collision.collisionPoint = closestLineApproachToAPoint;
			collision.timeOfImpact = timeOfImpact;
			collision.SetMotionsBeforeCollisions();
			return collision;
		}

		private static Vector3 GetIntersectionBetweenLines(Vector3 p1Line1, Vector3 p2Line1, Vector3 p1Line2, Vector3 p2Line2)
		{
			float num = p2Line1.z - p1Line1.z;
			float num2 = p1Line1.x - p2Line1.x;
			float num3 = num * p1Line1.x + num2 * p1Line1.z;
			float num4 = p2Line2.z - p1Line2.z;
			float num5 = p1Line2.x - p2Line2.x;
			float num6 = num4 * p1Line2.x + num5 * p1Line2.z;
			float num7 = num * num5 - num4 * num2;
			if (IsEqual(num7, 0f))
			{
				return new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
			}
			Vector3 zero = Vector3.zero;
			zero.x = (num3 * num5 - num2 * num6) / num7;
			zero.z = (num * num6 - num3 * num4) / num7;
			return zero;
		}

		private static Vector3 GetClosestLineApproachToAPoint(Vector3 p1Line1, Vector3 p2Line1, Vector3 point)
		{
			float num = p2Line1.z - p1Line1.z;
			float num2 = p1Line1.x - p2Line1.x;
			float num3 = num * p1Line1.x + num2 * p1Line1.z;
			float num4 = (0f - num2) * point.x + num * point.z;
			float num5 = num * num + num2 * num2;
			if (IsEqual(num5, 0f))
			{
				return point;
			}
			Vector3 zero = Vector3.zero;
			zero.x = (num * num3 - num2 * num4) / num5;
			zero.z = (num * num4 + num2 * num3) / num5;
			return zero;
		}

		public static bool TryGetSphereSphereCollision(ColliderBody cb1, ColliderBody cb2, out Collision collision)
		{
			PhysicsBody body = cb1.body;
			PhysicsBody body2 = cb2.body;
			collision = default(Collision);
			SphereShape sphereShape = body.shape as SphereShape;
			SphereShape sphereShape2 = body2.shape as SphereShape;
			float r = 0f;
			float r2 = 0f;
			if (sphereShape != null)
			{
				r = sphereShape.radius;
			}
			if (sphereShape2 != null)
			{
				r2 = sphereShape2.radius;
			}
			Vector3 vector = body.currentTransform.position.OnGround();
			Vector3 vector2 = cb1.nextTransform.position.OnGround();
			AABBBounds bounds = new AABBBounds(vector, vector2, sphereShape.radius);
			Vector3 vector3 = body2.currentTransform.position.OnGround();
			Vector3 vector4 = cb2.nextTransform.position.OnGround();
			AABBBounds bounds2 = new AABBBounds(vector3, vector4, sphereShape2.radius);
			if (!AABBBounds.CanCollide(ref bounds, ref bounds2))
			{
				stats.ballBallAABBFail++;
				return false;
			}
			stats.ballBallCheck++;
			if (TryGetSphereSphereCollision(body, body2, vector, vector2, r, vector3, vector4, r2, out collision))
			{
				collision.body1.transformAtCollision.rotation = Quaternion.Lerp(cb1.body.currentTransform.rotation, cb1.nextTransform.rotation, collision.timeOfImpact);
				collision.body2.transformAtCollision.rotation = Quaternion.Lerp(cb2.body.currentTransform.rotation, cb2.nextTransform.rotation, collision.timeOfImpact);
				return true;
			}
			return false;
		}

		public static bool TryGetSphereSphereCollision(PhysicsBody body1, PhysicsBody body2, Vector3 s1Start, Vector3 s1End, float r1, Vector3 s2Start, Vector3 s2End, float r2, out Collision collision)
		{
			collision = default(Collision);
			Vector3 vector = s1End - s1Start;
			Vector3 vector2 = s2End - s2Start;
			Vector3 vector3 = vector - vector2;
			Vector3 rhs = s2Start - s1Start;
			float num = r1 + r2;
			float magnitude = rhs.magnitude;
			float magnitude2 = vector3.magnitude;
			if (magnitude - num > magnitude2)
			{
				return false;
			}
			Vector3 normalized = vector3.normalized;
			float num2 = Vector3.Dot(normalized, rhs);
			if (num2 <= 0f)
			{
				return false;
			}
			float num3 = magnitude * magnitude - num2 * num2;
			float num4 = num * num;
			if (num3 >= num4)
			{
				return false;
			}
			float num5 = num4 - num3;
			if (num5 < 0f)
			{
				return false;
			}
			float num6 = num2 - Mathf.Sqrt(num5);
			if (magnitude2 < num6)
			{
				return false;
			}
			float num7 = 0f;
			if (magnitude2 > 0f)
			{
				num7 = num6 / magnitude2;
			}
			collision = Collision.indentity;
			collision.body1.body = body1;
			collision.body1.transformAtCollision.position = s1Start + vector * num7;
			collision.body2.body = body2;
			collision.body2.transformAtCollision.position = s2Start + vector2 * num7;
			Vector3 vector4 = collision.collidingBody2PositionAtCollision - collision.collidingBody1PositionAtCollision;
			collision.collisionPoint = collision.collidingBody1PositionAtCollision + vector4.normalized * r1;
			collision.timeOfImpact = Mathf.Abs(num7);
			collision.SetMotionsBeforeCollisions();
			return true;
		}
	}
}
